package wx;

import com.alibaba.fastjson.JSONObject;
import constant.Common;
import constant.Request;
import exception.CodeException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import util.DateUtil;
import util.HttpUtil;
import util.JsonUtil;
import util.StringUtil;
import wx.pojo.MiniProgram;
import wx.response.AuthorizationResponse;
import wx.template.BaseTemplate;

import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.security.spec.AlgorithmParameterSpec;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;

/**
 * 微信小程序相关接口
 *
 * @author zhanglinfeng
 * @date create in 2024/3/15 10:42
 */
public class WXAppUtil {
    private static Logger LOGGER = LoggerFactory.getLogger(WXAppUtil.class);

    /**
     * 失效时间：0，失效间隔天数：1
     */
    public final static int EXPIRE_TYPE_0 = 0;
    public final static int EXPIRE_TYPE_1 = 1;

    private static final String AUTHORIZATION_URL = "https://api.weixin.qq.com/sns/oauth2/access_token?appid=%s&secret=%s&code=%s&grant_type=authorization_code";
    private static final String TOKEN_URL = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=%s&secret=%s";
    private static final String URL_LINK_URL = "https://api.weixin.qq.com/wxa/generate_urllink?access_token=%s";

    /**
     * 授权code换取用户信息
     *
     * @param appId     appId
     * @param appSecret appSecret
     * @param code      授权code
     * @return AuthorizationResponse
     * @throws IOException 异常
     */
    public static AuthorizationResponse getAuthorizationInfo(String appId, String appSecret, String code) throws IOException {
        return JsonUtil.toObject(HttpUtil.httpsRequest(String.format(AUTHORIZATION_URL, appId, appSecret, code), Request.GET, null), AuthorizationResponse.class);
    }

    /**
     * 获取小程序、公众号token
     *
     * @param appId     appId
     * @param appSecret appSecret
     * @return accessToken
     */
    public static String getToken(String appId, String appSecret) throws IOException {
        String response = HttpUtil.httpsRequest(String.format(TOKEN_URL, appId, appSecret), Request.GET, null);
        Map<?, ?> map = JsonUtil.toMap(response);
        return (String) map.get("access_token");
    }

    /**
     * 获取小程序短链
     *
     * @param accessToken    token
     * @param path           路径
     * @param query          参数
     * @param expireType     失效类型
     * @param expireInterval 失效时间
     * @return urlLink
     */
    public static String getWXURLLink(String accessToken, String path, String query, int expireType, long expireInterval) {
        JSONObject requestDataStr = new JSONObject();
        requestDataStr.put("path", path);
        if (StringUtil.isNotEmpty(query)) {
            requestDataStr.put("query", query);
        }
        requestDataStr.put("env_version", "release");
        requestDataStr.put("is_expire", true);
        requestDataStr.put("expire_type", expireType);
        if (EXPIRE_TYPE_1 == expireType) {
            requestDataStr.put("expire_interval", expireInterval);
        } else {
            requestDataStr.put("expire_time", expireInterval / 1000L);
        }
        try {
            String response = HttpUtil.httpsRequest(String.format(URL_LINK_URL, accessToken), Request.POST, requestDataStr.toJSONString());
            Map<?, ?> responseMap = JsonUtil.toObject(response, Map.class);
            int errCode = Integer.parseInt(responseMap.get("errcode").toString());
            if (0 == errCode) {
                LOGGER.info("--小程序URL Link:{}", responseMap.get("url_link"));
                return (String) responseMap.get("url_link");
            } else {
                LOGGER.error("--获取小程序URL Link失败,requestDataStr:{}, responseMap:{}", requestDataStr.toJSONString(), JsonUtil.toString(responseMap));
                return Common.BLANK;
            }
        } catch (Exception e) {
            LOGGER.error("--获取小程序URL Link异常，请求参数：{}", requestDataStr.toJSONString(), e);
            return Common.BLANK;
        }
    }

    /**
     * 发送订阅消息
     *
     * @param accessToken token
     * @param page        跳转路径
     * @param template    模版
     * @return String
     * @throws IOException 异常
     */
    public static String sendSubscriptionMessage(String accessToken, String page, BaseTemplate template) throws IOException {
        // 获取小程序配置
        JSONObject requestDataStr = new JSONObject();
        requestDataStr.put("template_id", template.getTemplateId());
        if (StringUtil.isNotEmpty(page)) {
            requestDataStr.put("page", page);
        }
        requestDataStr.put("touser", template.getUserId());
        requestDataStr.put("data", template.getData(template.getUserName()));
        //跳转小程序类型：developer为开发版；trial为体验版；formal为正式版
        requestDataStr.put("miniprogram_state", "trial");
        requestDataStr.put("lang", "zh_CN");
        String requestData = requestDataStr.toJSONString();
        LOGGER.info("发送微信小程序订阅消息, requestData:{}", requestData);
        return HttpUtil.httpsRequest("https://api.weixin.qq.com/cgi-bin/message/subscribe/send?access_token=" + accessToken, Request.POST, requestData);
    }

    /**
     * 发送公众号消息，需要关注公众号，获取openId，设置IP白名单
     *
     * @param accessToken token
     * @param wxAppId     小程序appid
     * @param page        跳转路径
     * @param template    模版
     * @return String
     * @throws IOException 异常
     */
    public static String sendOfficialAccountMessage(String accessToken, String wxAppId, String page, BaseTemplate template) throws IOException {
        JSONObject requestDataStr = new JSONObject();
        requestDataStr.put("touser", template.getUserId());
        requestDataStr.put("template_id", template.getTemplateId());
        requestDataStr.put("data", template.getData(template.getUserName()));
        if (StringUtil.isNotEmpty(page)) {
            requestDataStr.put("miniprogram", new MiniProgram(wxAppId, page));
        }
        String requestData = requestDataStr.toJSONString();
        LOGGER.info("发送微信公众号消息, requestData:{}", requestData);
        return HttpUtil.httpsRequest("https://api.weixin.qq.com/cgi-bin/message/template/send?access_token=" + accessToken, Request.POST, requestData);
    }

    public String decryptWxData(String encryptedData, String iv, String sessionKey) {
        try {
            // 解密 decrypt
            byte[] encrypData = Base64.getDecoder().decode(encryptedData);
            byte[] ivData = Base64.getDecoder().decode(iv);
            byte[] sessionKeyByte = Base64.getDecoder().decode(sessionKey);
            AlgorithmParameterSpec ivSpec = new IvParameterSpec(ivData);
            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
            SecretKeySpec keySpec = new SecretKeySpec(sessionKeyByte, "AES");
            cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec);
            String resultString = new String(cipher.doFinal(encrypData), StandardCharsets.UTF_8);
            LOGGER.info("解密信息:{}", resultString);
            return resultString;
        } catch (Exception e) {
            LOGGER.error("解密微信用户信息失败, sessionKey:{}, encryptedData:{}, iv:{}", sessionKey, encryptedData, iv, e);
            throw new CodeException("DECRYPT_ERROR", "解密信息失败，请重试");
        }
    }

    /**
     * 获取小程序二维码
     *
     * @param accessToken token
     * @param scene       参数
     * @param page        跳转路径
     * @return BufferedInputStream
     */
    public String getQrcode(String accessToken, String scene, String page, String filePath) {
        String fileName = DateUtil.nowStr(DateUtil.YYYYMMDDHHMMSS) + ".png";
        File file = new File(filePath + fileName);
        try (OutputStream out = new FileOutputStream(file)) {
            URL wxAppQRCodePathUrl = new URL("https://api.weixin.qq.com/wxa/getwxacodeunlimit?access_token=" + accessToken);
            HttpURLConnection httpURLConnection = (HttpURLConnection) wxAppQRCodePathUrl.openConnection();
            httpURLConnection.setRequestMethod("POST");
            httpURLConnection.setDoOutput(true);
            httpURLConnection.setDoInput(true);
            PrintWriter printWriter = new PrintWriter(httpURLConnection.getOutputStream());
            Map<String, Object> reqMap = new HashMap<>();
            reqMap.put("scene", scene);
            reqMap.put("page", page);
            reqMap.put("check_path", false);
            printWriter.write(JsonUtil.toString(reqMap));
            printWriter.flush();
            try (BufferedInputStream bis = new BufferedInputStream(httpURLConnection.getInputStream())) {
                int len;
                byte[] arr = new byte[1024];
                while ((len = bis.read(arr)) != -1) {
                    out.write(arr, 0, len);
                    out.flush();
                }
            }
            return fileName;
        } catch (Exception e) {
            throw new CodeException("INFO_ERROR", "获取小程序二维码异常");
        }
    }
}
